home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 June / PersonalComputerWorld-June2009-CoverdiscCD.iso / Software / Freeware / Firebug 1.3.3 / firebug-1.3.3-fx.xpi / content / firebug / tabCache.js < prev    next >
Encoding:
JavaScript  |  2009-02-19  |  14.1 KB  |  498 lines

  1. /* See license.txt for terms of usage */
  2.  
  3. FBL.ns(function() { with (FBL) {
  4.  
  5. // ************************************************************************************************
  6. // Constants
  7.  
  8. const Cc = Components.classes;
  9. const Ci = Components.interfaces;
  10.  
  11. const httpObserver = Cc["@joehewitt.com/firebug-http-observer;1"].getService(Ci.nsIObserverService);
  12. const ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
  13.  
  14. // List of text content types. These content-types are cached.
  15. var contentTypes =
  16. {
  17.     "text/plain": 1,
  18.     "text/html": 1,
  19.     "text/xml": 1,
  20.     "text/xsl": 1,
  21.     "text/xul": 1,
  22.     "text/css": 1,
  23.     "text/sgml": 1,
  24.     "text/rtf": 1,
  25.     "text/richtext": 1,
  26.     "text/x-setext": 1,
  27.     "text/rtf": 1,
  28.     "text/richtext": 1,
  29.     "text/javascript": 1,
  30.     "text/jscript": 1,
  31.     "text/tab-separated-values": 1,
  32.     "text/rdf": 1,
  33.     "text/xif": 1,
  34.     "text/ecmascript": 1,
  35.     "text/vnd.curl": 1,
  36.     "text/x-json": 1,
  37.     "text/x-js": 1,
  38.     "text/js": 1,
  39.     "text/vbscript": 1,
  40.     "view-source": 1,
  41.     "view-fragment": 1,
  42.     "application/xml": 1,
  43.     "application/xhtml+xml": 1,
  44.     "application/vnd.mozilla.xul+xml": 1,
  45.     "application/javascript": 1,
  46.     "application/x-javascript": 1,
  47.     "application/x-httpd-php": 1,
  48.     "application/rdf+xml": 1,
  49.     "application/ecmascript": 1,
  50.     "application/http-index-format": 1,
  51.     "application/json": 1,
  52.     "application/x-js": 1,
  53. };
  54.  
  55. // Maximum cached size of a signle response (bytes)
  56. var responseSizeLimit = 1024 * 1024 * 5;
  57.  
  58. // ************************************************************************************************
  59. // Model implementation
  60.  
  61. /**
  62.  * Implementation of cache model. The only purpose of this object is to register an HTTP 
  63.  * observer so, HTTP communication can be interecepted and all incoming data stored within
  64.  * a cache.
  65.  */
  66. Firebug.TabCacheModel = extend(Firebug.Module, 
  67. {
  68.     initializeUI: function(owner)
  69.     {
  70.         var mimeTypes = Firebug.getPref(Firebug.prefDomain, "cache.mimeTypes");
  71.         if (mimeTypes) {
  72.             var list = mimeTypes.split(" ");
  73.             for (var i=0; i<list.length; i++)
  74.                 contentTypes[list[i]] = 1;
  75.  
  76.         }
  77.  
  78.         // Read maximum size limit for cached response from preferences.
  79.         responseSizeLimit = Firebug.getPref(Firebug.prefDomain, "cache.responseLimit");
  80.  
  81.         // Register for HTTP events.
  82.         if (Ci.nsITraceableChannel)
  83.             httpObserver.addObserver(this, "firebug-http-event", false);
  84.     },
  85.  
  86.     shutdown: function()
  87.     {
  88.         if (Ci.nsITraceableChannel)
  89.             httpObserver.removeObserver(this, "firebug-http-event");
  90.     },
  91.  
  92.     initContext: function(context)
  93.     {
  94.     },
  95.  
  96.     /* nsIObserver */
  97.     observe: function(subject, topic, data)
  98.     {
  99.         try 
  100.         {
  101.             if (!(subject instanceof Ci.nsIHttpChannel))
  102.                 return;
  103.  
  104.             var win = getWindowForRequest(subject);
  105.             var tabId = Firebug.getTabIdForWindow(win);
  106.             if (!(tabId && win))
  107.                 return;
  108.  
  109.             if (topic == "http-on-modify-request")
  110.                 this.onModifyRequest(subject, win, tabId);
  111.             else if (topic == "http-on-examine-response")
  112.                 this.onExamineResponse(subject, win, tabId);
  113.             else if (topic == "http-on-examine-cached-response")
  114.                 this.onCachedResponse(subject, win, tabId);
  115.         }
  116.         catch (err)
  117.         {
  118.         }
  119.     },
  120.  
  121.     onModifyRequest: function(request, win, tabId)
  122.     {
  123.     },
  124.  
  125.     onExamineResponse: function(request, win, tabId)
  126.     {
  127.         try 
  128.         {
  129.             // Register traceable channel listener in order to intercept all incoming data for 
  130.             // this context/tab. nsITraceableChannel interface is introduced in Firefox 3.0.4
  131.             request.QueryInterface(Ci.nsITraceableChannel);
  132.             var newListener = new TracingListener(win);
  133.             newListener.listener = request.setNewListener(newListener);
  134.         }
  135.         catch (err)
  136.         {
  137.         }
  138.     },
  139.  
  140.     onCachedResponse: function(request, win, tabId)
  141.     {
  142.         // Make sure cached responses are observed with nsITraceableChannel too.
  143.         this.onExamineResponse(request, win, tabId);
  144.     }
  145. });
  146.  
  147. // ************************************************************************************************
  148.  
  149. /**
  150.  * This cache object is intended to cache all responses made by a specific tab.
  151.  * The implementation is based on nsITraceableChannel interface introduced in 
  152.  * Firefox 3.0.4. This interface allows to intercept all incoming HTTP data.
  153.  *
  154.  * This object replaces the SourceCache, which still exist only for backward 
  155.  * compatibility.
  156.  *
  157.  * The object is derived from SourceCache so, the same interface and most of the
  158.  * implementation is used.
  159.  */
  160. Firebug.TabCache = function(win, context)
  161. {
  162.     Firebug.SourceCache.call(this, win, context);
  163. };
  164.  
  165. Firebug.TabCache.prototype = extend(Firebug.SourceCache.prototype,
  166. {
  167.     listeners: [],
  168.     responses: [],       // responses in progress.
  169.  
  170.     storePartialResponse: function(request, responseText, win)
  171.     {
  172.         try
  173.         {
  174.             responseText = FBL.convertToUnicode(responseText, win.document.characterSet);
  175.         }
  176.         catch (err)
  177.         {
  178.         }
  179.  
  180.         var url = safeGetName(request);
  181.         var response = this.getResponse(request);
  182.  
  183.         // Size of each response is limited.
  184.         var limitNotReached = true;
  185.         if (response.size + responseText.length >= responseSizeLimit)
  186.         {
  187.             limitNotReached = false;
  188.             responseText = responseText.substr(0, responseSizeLimit - response.size);
  189.             FBTrace.sysout("tabCache.storePartialResponse Max size limit reached for: " + url);
  190.         }
  191.  
  192.         response.size += responseText.length;
  193.  
  194.         // Store partial content into the cache.
  195.         this.store(url, responseText);
  196.  
  197.         // Return false if furhter parts of this response should be ignored.
  198.         return limitNotReached;
  199.     },
  200.  
  201.     getResponse: function(request)
  202.     {
  203.         var url = safeGetName(request);
  204.         var response = this.responses[url];
  205.         if (!response)
  206.         {
  207.             this.invalidate(url);
  208.             this.responses[url] = response = {
  209.                 request: request,
  210.                 size: 0
  211.             };
  212.         }
  213.  
  214.         return response;
  215.     },
  216.  
  217.     startRequest: function(request)
  218.     {
  219.         // Make sure the response-entry (used to count total response size) is properly 
  220.         // initialized (cleared) now. If no data is received, the response entry remains empty.
  221.         var response = this.getResponse(request);
  222.  
  223.     },
  224.  
  225.     stopRequest: function(request)
  226.     {
  227.         var url = safeGetName(request);
  228.         delete this.responses[url];
  229.  
  230.         dispatch(this.listeners, "onStoreResponse", [this.window, request, this.cache[url]]);
  231.     },
  232.  
  233.     storeSplitLines: function(url, lines)
  234.     {
  235.         var currLines = this.cache[url];
  236.         if (!currLines)
  237.             currLines = this.cache[url] = [];
  238.  
  239.         // Join the last line with the new first one so, the source code 
  240.         // lines are properly formatted.
  241.         if (currLines.length)
  242.             currLines[currLines.length-1] += lines.shift();
  243.  
  244.         // Append new lines (if any) into the array for specified url.
  245.         if (lines.length)
  246.             this.cache[url] = currLines.concat(lines);
  247.  
  248.         return this.cache[url];
  249.     },
  250.  
  251.     loadFromCache: function(url, method, file)
  252.     {
  253.         // The ancestor implementation (SourceCache) uses ioService.newChannel, which 
  254.         // can result in additional request to the server (in case the response can't 
  255.         // be loaded from the Firefox cache) - known as double-load problem.
  256.         // This new implementation (TabCache) uses nsITraceableListener so, all responses
  257.         // should be already cached.
  258.  
  259.         // xxxHonza: let's try to get the response from the cache till #449198 is fixed.
  260.         var stream;
  261.         var responseText;
  262.         try 
  263.         {
  264.             var channel = ioService.newChannel(url, null, null);
  265.  
  266.             // These flag combination doesn't repost the request.
  267.             channel.loadFlags = Ci.nsIRequest.LOAD_FROM_CACHE |
  268.                 Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE |
  269.                 Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
  270.  
  271.             var charset = "UTF-8";
  272.             var doc = this.context.window.document;
  273.             if (doc)
  274.                 charset = doc.characterSet;
  275.  
  276.             stream = channel.open();
  277.             responseText = readFromStream(stream, charset);
  278.  
  279.             responseText = this.store(url, responseText);
  280.         }
  281.         catch (err) 
  282.         {
  283.         }
  284.         finally
  285.         {
  286.             if(stream)
  287.                 stream.close(); 
  288.         }
  289.  
  290.         return responseText;
  291.     },
  292.     
  293.     // Listeners
  294.     addListener: function(listener)
  295.     {
  296.         this.listeners.push(listener);
  297.     },
  298.  
  299.     removeListener: function(listener)
  300.     {
  301.         remove(this.listeners, listener);
  302.     }    
  303. });
  304.  
  305. // ************************************************************************************************
  306. // TracingListener implementation
  307.  
  308. /**
  309.  * This object implements nsIStreamListener interface and is intended to monitor all network 
  310.  * channels (nsIHttpChannel). For every channel a new instance of this object is created and 
  311.  * registered. See Firebug.TabCacheModel.onExamineResponse method.
  312.  */
  313. function TracingListener(win)
  314. {
  315.     this.window = win;
  316.     this.listener = null;
  317.     this.endOfLine = false;
  318.     this.ignore = false;
  319. }
  320.  
  321. TracingListener.prototype = 
  322. {
  323.     onCollectData: function(request, inputStream, offset, count)
  324.     {
  325.         try
  326.         {
  327.             var binaryInputStream = CCIN("@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream");
  328.             var storageStream = CCIN("@mozilla.org/storagestream;1", "nsIStorageStream");
  329.             var binaryOutputStream = CCIN("@mozilla.org/binaryoutputstream;1", "nsIBinaryOutputStream");
  330.             
  331.             binaryInputStream.setInputStream(inputStream);
  332.             storageStream.init(8192, count, null);
  333.             binaryOutputStream.setOutputStream(storageStream.getOutputStream(0));
  334.  
  335.             var data = binaryInputStream.readBytes(count);
  336.             binaryOutputStream.writeBytes(data, count);
  337.  
  338.             // Avoid creating additional empty line if response comes in more pieces 
  339.             // and the split is made just between "\r" and "\n" (Win line-end).
  340.             // So, if the response starts with "\n" while the previous part ended with "\r",
  341.             // remove the first character.
  342.             if (this.endOfLine && data.length && data[0] == "\n")
  343.                 data = data.substring(1);
  344.  
  345.             if (data.length)
  346.                 this.endOfLine = data[data.length-1] == "\r";
  347.  
  348.             // At this moment, initContext is alredy called so, the context is
  349.             // ready and associated with the window.
  350.             var context = TabWatcher.getContextByWindow(this.window);
  351.             if (context) 
  352.             {
  353.                 // Store received data into the cache as they come.
  354.                 if (!context.sourceCache.storePartialResponse(request, data, this.window))
  355.                     this.ignore = true;
  356.             }
  357.             else 
  358.             {
  359.             }
  360.  
  361.             // Let other listeners use the stream.
  362.             return storageStream.newInputStream(0);
  363.         }
  364.         catch (err)
  365.         {
  366.         }
  367.  
  368.         return null;
  369.     },
  370.  
  371.     /* nsIStreamListener */
  372.     onDataAvailable: function(request, requestContext, inputStream, offset, count)
  373.     {
  374.         try
  375.         {
  376.             if (!this.ignore) 
  377.             {
  378.                 // Cache only text responses for now.
  379.                 var contentType = request.contentType;
  380.                 if (contentType)
  381.                     contentType = contentType.split(";")[0];
  382.  
  383.                 if (contentTypes[contentType])
  384.                 {
  385.                     var newStream = this.onCollectData(request, inputStream, offset, count);
  386.                     if (newStream)
  387.                         inputStream = newStream;
  388.                 }
  389.                 else
  390.                 {
  391.                 }
  392.             }
  393.         }
  394.         catch (err)
  395.         {
  396.         }
  397.  
  398.         try
  399.         {
  400.             if (this.listener)
  401.                 this.listener.onDataAvailable(request, requestContext, inputStream, offset, count);
  402.         }
  403.         catch (err)
  404.         {
  405.         }
  406.     },
  407.  
  408.     onStartRequest: function(request, requestContext)
  409.     {
  410.         try
  411.         {
  412.             var context = TabWatcher.getContextByWindow(this.window);
  413.             if (context)
  414.             {
  415.                 context.sourceCache.startRequest(request);
  416.             }
  417.             else
  418.             {
  419.             }
  420.         }
  421.         catch (err)
  422.         {
  423.         }
  424.  
  425.         try
  426.         {
  427.             if (this.listener)
  428.                 this.listener.onStartRequest(request, requestContext);
  429.         }
  430.         catch (err)
  431.         {
  432.         }
  433.     },
  434.  
  435.     onStopRequest: function(request, requestContext, statusCode)
  436.     {
  437.         try
  438.         {
  439.             var context = TabWatcher.getContextByWindow(this.window);
  440.             if (context)
  441.             {
  442.                 context.sourceCache.stopRequest(request);
  443.             }
  444.             else
  445.             {
  446.             }
  447.         }
  448.         catch (err)
  449.         {
  450.         }
  451.  
  452.         try
  453.         {
  454.             if (this.listener)
  455.                 this.listener.onStopRequest(request, requestContext, statusCode);
  456.         }
  457.         catch (err)
  458.         {
  459.         }
  460.     },
  461.  
  462.     /* nsISupports */
  463.     QueryInterface: function(iid)
  464.     {
  465.         if (iid.equals(Ci.nsIStreamListener) ||
  466.             iid.equals(Ci.nsISupportsWeakReference) ||
  467.             iid.equals(Ci.nsISupports))
  468.         {
  469.             return this;
  470.         }
  471.  
  472.         throw Components.results.NS_NOINTERFACE;
  473.     }
  474. }
  475.  
  476. // ************************************************************************************************
  477. // Helpers
  478.  
  479. function safeGetName(request)
  480. {
  481.     try {
  482.         return request.name;
  483.     }
  484.     catch (exc) { 
  485.     }
  486.  
  487.     return null;
  488. }
  489.  
  490. // ************************************************************************************************
  491. // Registration
  492.  
  493. Firebug.registerModule(Firebug.TabCacheModel);
  494.  
  495. // ************************************************************************************************
  496.  
  497. }});
  498.